Go语言切片【复制(copy 、append)、剪切、删除(append、copy、len)等操作

您所在的位置:网站首页 golang slice 拷贝 Go语言切片【复制(copy 、append)、剪切、删除(append、copy、len)等操作

Go语言切片【复制(copy 、append)、剪切、删除(append、copy、len)等操作

2024-03-04 03:34| 来源: 网络整理| 查看: 265

阅读目录 复制(copy 、append)剪切删除剪切或删除操作可能引起的内存泄露剪切删除删除但不保留元素原有顺序内部扩张尾部扩张过滤插入追加弹出前插翻转打乱顺序 使用最小分配进行批处理就地删除重复元素(元素可比较)元素存在就移到最前面,不存在则放在前面滑动窗口

复制(copy 、append)

将切片 a 中的元素复制到切片b中。

最简单的、最常用的方法就是使用内置的 copy 函数。

package main import "fmt" func main() { // 初始化切片 a a := []int{1, 2, 3, 4, 5} // 创建切片 b,长度与 a 相同 b := make([]int, len(a)) copy(b, a) // 打印切片 a 和 b 的内容 fmt.Println("切片 a:", a) fmt.Println("切片 b:", b) }

除了使用内置的 copy 函数外,还有下面两种使用 append 函数复制切片的方法。

package main import ( "fmt" ) func main() { // 初始化切片 a a := []int{1, 2, 3, 4, 5} // 使用 append([]T(nil), a...) 复制切片 b1 := append([]int(nil), a...) // 使用 append(a[:0:0], a...) 复制切片 b2 := append(a[:0:0], a...) // 修改 b1 或 b2 不会影响原始切片 a b1[0] = 100 b2[0] = 200 // 打印切片 a, b1 和 b2 的内容 fmt.Println("切片 a:", a) fmt.Println("切片 b1:", b1) fmt.Println("切片 b2:", b2) } 剪切

将切片 a 中索引 i~j 位置的元素剪切掉。

可以按照下面的方式,使用 append 函数完成。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("原始切片:", a) // 要剪切的起始和结束索引 i := 2 j := 6 // 执行剪切操作 a = append(a[:i], a[j:]...) fmt.Println("剪切后的切片:", a) }

append(a[:i], a[j:]...) 语句将 a 的索引从 2 到 6 的部分剪切掉,并将剩余的部分重新赋值给 a,实现了剪切操作。

PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5 6 7 8 9 10] 剪切后的切片: [1 2 7 8 9 10] PS E:\TEXT\test>

请注意,这种方法虽然在某些情况下是有效的,但不适用于所有场景。在处理大型切片时,剪切操作可能会引发性能问题。在实际使用中,你可能需要根据具体情况考虑是否使用剪切操作,或者考虑其他更适合的方式来处理切片。

删除

将切片 a 中索引位置为 i 的元素删除。

同样可以按照上面剪切的方式使用 append 函数完成删除操作。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要删除的元素的索引 i := 2 // 执行删除操作 a = append(a[:i], a[i+1:]...) fmt.Println("删除后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 删除后的切片: [1 2 4 5] PS E:\TEXT\test>

或者搭配 copy 函数使用切片表达式完成删除操作。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要删除的元素的索引 i := 2 // 删除指定索引处的元素 a = a[:i+copy(a[i:], a[i+1:])] fmt.Println("删除后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 删除后的切片: [1 2 4 5] PS E:\TEXT\test>

此外,如果只需要删除掉索引为 i 的元素,无需保留切片元素原有的顺序,那么还可以使用下面这种简单的方式进行删除。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要删除的元素的索引 i := 2 // 将最后一个元素移到索引i处 a[i] = a[len(a)-1] // 截掉最后一个元素 a = a[:len(a)-1] fmt.Println("删除后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 删除后的切片: [1 2 5 4] PS E:\TEXT\test> 剪切或删除操作可能引起的内存泄露

需要特别注意的是:

如果切片 a 中的元素是一个指针类型或包含指针字段的结构体类型(需要被垃圾回收),上面剪切和删除的示例代码会存在一个潜在的内存泄漏问题:

一些具有值的元素仍被切片 a 引用,因此无法被垃圾回收机制回收掉。 下面的代码可以解决这个问题。

剪切 copy(a[i:], a[j:]) for k, n := len(a)-j+i, len(a); k < n; k++ { a[k] = nil // 或类型T的零值 } a = a[:len(a)-j+i] package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("原始切片:", a) // 要删除的起始和结束索引 i := 2 j := 6 // 使用 copy 删除指定范围的元素 copy(a[i:], a[j:]) // 将多余的元素设置为零值 for k, n := len(a)-j+i, len(a); k go run . 原始切片: [1 2 3 4 5 6 7 8 9 10] 删除后的切片: [1 2 7 8 9 10] PS E:\TEXT\test>

在这个示例中,我们首先使用 copy 函数来将索引从 j 开始的元素复制到索引从 i 开始的位置。然后,通过一个循环,我们将剩余的多余元素设置为零值。最后,我们通过切片操作 a[:len(a)-j+i] 来截断切片,实现了删除操作。

删除 copy(a[i:], a[i+1:]) a[len(a)-1] = nil // 或类型T的零值 a = a[:len(a)-1] package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("原始切片:", a) // 要删除的元素的索引 i := 2 // 使用 copy 删除指定索引的元素 copy(a[i:], a[i+1:]) // 将最后一个元素设置为零值 a[len(a)-1] = 0 // 或者使用类型的零值,例如 a[len(a)-1] = "" // 截断切片 a = a[:len(a)-1] fmt.Println("删除后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5 6 7 8 9 10] 删除后的切片: [1 2 4 5 6 7 8 9 10] PS E:\TEXT\test> 删除但不保留元素原有顺序 a[i] = a[len(a)-1] a[len(a)-1] = nil a = a[:len(a)-1] package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要删除的元素的索引 i := 2 // 将要删除的元素替换为最后一个元素 a[i] = a[len(a)-1] // 将最后一个元素置为零值 a[len(a)-1] = 0 // 或者使用类型的零值,例如 a[len(a)-1] = "" // 截断切片 a = a[:len(a)-1] fmt.Println("删除后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 删除后的切片: [1 2 5 4] PS E:\TEXT\test> 内部扩张

在切片 a 的索引 i 之后扩张 j 个元素。

使用两个 append 函数完成,即先将索引i之后的元素追加到一个长度为 j 的切片后,再将这个切片中的所有元素追加到切片 a 的索引i之后。

a = append(a[:i], append(make([]T, j), a[i:]...)...) package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要删除的起始和结束索引 i := 1 j := 2 // 删除指定范围的元素并在内部插入新元素 a = append(a[:i], append(make([]int, j), a[i:]...)...) fmt.Println("操作后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 操作后的切片: [1 0 0 2 3 4 5] PS E:\TEXT\test>

扩张的这一部分元素为T类型的零值。

尾部扩张

将切片 a 的尾部扩张 j 个元素的空间。

a = append(a, make([]T, j)...)

扩张的这一部分元素同样为T类型的零值。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要插入的元素数量 j := 3 // 在切片末尾插入 j 个零值元素 a = append(a, make([]int, j)...) fmt.Println("操作后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 操作后的切片: [1 2 3 4 5 0 0 0] PS E:\TEXT\test> 过滤

按照一定的规则将切片 a 中的元素进行就地过滤。

这里假设过滤的条件已封装为 keep 函数,使用 for range 遍历切片 a 的所有元素逐一调用 keep 函数进行过滤。

n := 0 for _, x := range a { if keep(x) { a[n] = x // 保留该元素 n++ } } a = a[:n] // 截取切片中需保留的元素 package main import "fmt" func keep(x int) bool { // 这里可以定义保留元素的条件 return x%2 == 0 // 保留偶数 } func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("原始切片:", a) n := 0 for _, x := range a { if keep(x) { a[n] = x // 保留该元素 n++ } } a = a[:n] // 截取切片中需保留的元素 fmt.Println("保留后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5 6 7 8 9 10] 保留后的切片: [2 4 6 8 10] PS E:\TEXT\test> 插入

将元素 x 插入切片 a 的索引i处。

还是使用两个 append 函数完成插入 x 的操作。

a = append(a[:i], append([]T{x}, a[i:]...)...) package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要插入的元素 x := 6 // 要插入的位置 i := 2 // 在索引 i 处插入元素 x a = append(a[:i], append([]int{x}, a[i:]...)...) fmt.Println("插入后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 插入后的切片: [1 2 6 3 4 5] PS E:\TEXT\test>

第二个 append 函数创建了一个具有自己底层数组的新切片,并将 a[i:] 中的元素复制到该切片,然后由第一个 append 函数将这些元素复制回切片 a。

我们可以通过使用另一种方法来避免新切片的创建(以及由此产生的内存垃圾)和第二个副本:

a = append(a, 0 /* 这里应使用元素类型的零值 */) copy(a[i+1:], a[i:]) a[i] = x package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要插入的元素 x := 6 // 要插入的位置 i := 2 // 在索引 i 处插入元素 x a = append(a, 0 /* 元素类型的零值 */) copy(a[i+1:], a[i:]) a[i] = x fmt.Println("插入后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 插入后的切片: [1 2 6 3 4 5] PS E:\TEXT\test> 追加

将元素 x 追加到切片 a 的最后。

这里使用 append 函数即可。

a = append(a, x) package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 要添加的元素 x := 6 // 在切片末尾添加元素 x a = append(a, x) fmt.Println("添加元素后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 添加元素后的切片: [1 2 3 4 5 6] PS E:\TEXT\test> 弹出

将切片 a 的最后一个元素弹出。 这里使用切片表达式完成弹出操作。

x, a = a[len(a)-1], a[:len(a)-1] package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 将最后一个元素取出并赋值给变量 x x, a := a[len(a)-1], a[:len(a)-1] fmt.Println("取出的元素:", x) fmt.Println("删除最后一个元素后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 取出的元素: 5 删除最后一个元素后的切片: [1 2 3 4] PS E:\TEXT\test>

弹出切片 a 的第一个元素。

x, a = a[0], a[1:] package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 将第一个元素取出并赋值给变量 x x, a := a[0], a[1:] fmt.Println("取出的元素:", x) fmt.Println("删除第一个元素后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 取出的元素: 1 删除第一个元素后的切片: [2 3 4 5] PS E:\TEXT\test> 前插

将元素 x 前插到切片 a 的开始。

a = append([]T{x}, a...) package main import "fmt" func main() { // 创建一个切片 a := []int{2, 3, 4, 5} fmt.Println("原始切片:", a) // 要插入的元素 x := 1 // 在切片开头插入元素 x a = append([]int{x}, a...) fmt.Println("插入元素后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [2 3 4 5] 插入元素后的切片: [1 2 3 4 5] PS E:\TEXT\test> 翻转

将切片 a 的元素顺序翻转。

通过迭代两两互换元素完成。

b := a[:0] for _, x := range a { if f(x) { b = append(b, x) } } package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 将切片中的元素逆序排列 for i := len(a)/2 - 1; i >= 0; i-- { opp := len(a) - 1 - i a[i], a[opp] = a[opp], a[i] } fmt.Println("逆序排列后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 逆序排列后的切片: [5 4 3 2 1] PS E:\TEXT\test>

len(a)/2 - 1 是一个计算,用于找到切片 a 的中间索引的前一个索引。这个计算的目的通常是为了在逆序排列切片元素时使用,以确保正确地交换元素。

假设切片 a 的长度为奇数,例如长度为 5,那么中间的元素索引为 2。在逆序排列时,我们可以从中间元素开始,往两侧交换元素。因此,如果我们从中间索引 2 开始,逆序排列的范围将覆盖到索引 0 和 4,这样就足够了。

然而,如果切片 a 的长度为偶数,例如长度为 6,那么中间的元素索引为 2 和 3。如果我们从中间索引 2 开始,逆序排列的范围将覆盖到索引 0、1、3 和 4,这会导致中间的两个元素被交换两次。因此,在这种情况下,我们只需从索引 1 开始逆序排列,以避免重复交换。

综上所述,len(a)/2 - 1 是一个常用的计算,用于找到逆序排列时需要处理的范围的起始索引。

package main import "fmt" func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 逆序排列切片中的元素 for left, right := 0, len(a)-1; left go run . 原始切片: [1 2 3 4 5] 逆序排列后的切片: [5 4 3 2 1] PS E:\TEXT\test> 打乱顺序

打乱切片 a 中元素的顺序。

Fisher–Yates 算法:

package main import ( "fmt" "math/rand" "time" ) func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 初始化随机数生成器 rand.Seed(time.Now().UnixNano()) // 使用 Fisher-Yates 洗牌算法随机打乱切片中的元素 for i := len(a) - 1; i > 0; i-- { j := rand.Intn(i + 1) a[i], a[j] = a[j], a[i] } fmt.Println("随机打乱后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 随机打乱后的切片: [2 4 5 3 1] PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 随机打乱后的切片: [5 1 4 3 2] PS E:\TEXT\test>

从go1.10开始,可以使用math/rand.Shuffle。

rand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) package main import ( "fmt" "math/rand" "time" ) func main() { // 创建一个切片 a := []int{1, 2, 3, 4, 5} fmt.Println("原始切片:", a) // 初始化随机数生成器 rand.Seed(time.Now().UnixNano()) // 使用 rand.Shuffle 函数随机洗牌切片中的元素 rand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) fmt.Println("随机洗牌后的切片:", a) } PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 随机洗牌后的切片: [2 1 4 5 3] PS E:\TEXT\test> go run . 原始切片: [1 2 3 4 5] 随机洗牌后的切片: [1 5 3 2 4] PS E:\TEXT\test> 使用最小分配进行批处理

如果你想对一个大型切片 a 的元素分批进行处理,这会很有用。

package main import "fmt" func main() { actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} batchSize := 3 batches := make([][]int, 0, (len(actions)+batchSize-1)/batchSize) for batchSize go run . 初始批次: [[0 1 2] [3 4 5] [6 7 8] [9]] PS E:\TEXT\test> 就地删除重复元素(元素可比较) package main import ( "fmt" "sort" ) func main() { in := []int{3, 2, 1, 4, 3, 2, 1, 4, 1} sort.Ints(in) j := 0 for i := 1; i go run . [1 2 3 4] PS E:\TEXT\test>

在这个示例中,我们首先使用 sort.Ints 对切片 in 进行排序。

然后,使用循环遍历排序后的切片,将不重复的元素移到切片的前部。通过这种方式,j 记录了当前不重复元素的索引。最后,通过 result := in[:j+1] 可以得到一个只包含不重复元素的切片。

这种方法可以有效地移除重复元素,并且保持了原始元素的相对顺序。

元素存在就移到最前面,不存在则放在前面 package main import "fmt" func moveToFront(needle string, haystack []string) []string { if len(haystack) != 0 && haystack[0] == needle { return haystack } prev := needle for i, elem := range haystack { switch { case i == 0: haystack[0] = needle prev = elem case elem == needle: haystack[i] = prev return haystack default: haystack[i] = prev prev = elem } } return append(haystack, prev) } func main() { haystack := []string{"a", "b", "c", "d", "e"} fmt.Println("初始切片:", haystack) haystack = moveToFront("c", haystack) fmt.Println("移动 c 到前面:", haystack) haystack = moveToFront("f", haystack) fmt.Println("移动 f 到前面:", haystack) } PS E:\TEXT\test> go run . 初始切片: [a b c d e] 移动 c 到前面: [c a b d e] 移动 f 到前面: [f c a b d e] PS E:\TEXT\test>

在这个示例中,我们首先定义了 moveToFront 函数,然后创建了一个初始的字符串切片 haystack。通过调用 moveToFront 函数,我们可以将指定的字符串移动到切片的最前面,同时保持其他元素的相对顺序。

这个示例展示了如何在切片中移动元素并维护元素的相对位置,以及如何在需要时将新的元素添加到切片的前面。

滑动窗口

将切片 input 生成 size 大小的滑动窗口。

package main import "fmt" func slidingWindow(size int, input []int) [][]int { // 返回入参的切片作为第一个元素 if len(input)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3